﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Lime49;

namespace LockCrypt.Core {
    /// <summary>
    /// A group of accounts
    /// </summary>
    public class GroupBase : IGroup, IComparable {
        public virtual string ID { get; set; }
        public virtual string Name { get; set; }
        public virtual string Icon { get; set; }
        public virtual IList<IGroup> Groups { get; set; }
        public virtual IList<IAccount> Accounts { get; set; }
        public virtual IList<INestable> Children {
            get {
                List<INestable> children = new List<INestable>(Accounts.Cast<INestable>());
                children.AddRange(Groups.Cast<INestable>());
                return children;
            }
        }
        public virtual string ParentID { get; set; }
        public virtual int SortIndex { get; set; }

        /// <summary>
        /// Initializes a new <see cref="GroupBase"/>.
        /// </summary>
        public GroupBase() {
            ID = Lime49.Utils.GenID(true);
            Icon = Constants.DefaultGroupIcon;
            Groups = new List<IGroup>();
            Accounts = new List<IAccount>();
        }

        /// <summary>
        /// Finds a the child group by recursively searching this Group and it's children.
        /// </summary>
        /// <param name="groupId">The group ID to find.</param>
        /// <returns>A Group with a matching ID, or <c>null</c></returns>
        public IGroup FindGroup(string groupId) {
            if(this.ID == groupId) {
                return this;
            } else {
                foreach(IGroup childGroup in this.Groups) {
                    IGroup group = childGroup.FindGroup(groupId);
                    if(group != null) {
                        return group;
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// Flattens this group and all child groups (direct and indirect descendants).
        /// </summary>
        /// <returns>This group and all groups it contains.</returns>
        public IEnumerable<IGroup> Flatten() {
            List<IGroup> children = new List<IGroup>();
            foreach(IGroup childGroup in this.Groups) {
                children.AddRange(childGroup.Flatten());
            }
            children.Add(this);
            return children;
        }


        /// <summary>
        /// Converts this group to an XElement.
        /// </summary>
        /// <returns>An XElement representing this group.</returns>
        public XElement ToXElement() {
            return new XElement("group",
                                new XElement("title", HtmlUtils.HtmlEncode(this.Name)),
                                new XElement("icon", HtmlUtils.HtmlEncode(this.Icon)),
                                new XAttribute("id", HtmlUtils.HtmlEncode(this.ID)),
                                new XElement("parentid", HtmlUtils.HtmlEncode(this.ParentID)),
                                new XAttribute("sortindex", this.SortIndex));
        }

        /// <summary>
        /// Initializes a new <see cref="GroupBase"/> from an XElement.
        /// </summary>
        /// <param name="element">The XElement containing the group data.</param>
        /// <returns>The group contained in the specified XElement.</returns>
        public static IGroup FromXElement(XElement element) {
            try {
                string parentId = HtmlUtils.HtmlDecode(element.Element("parentid").Value);
                IGroup group = new GroupBase() {
                    Name = HtmlUtils.HtmlDecode(element.Element("title").Value),
                    Icon = HtmlUtils.HtmlDecode(element.Element("icon").Value),
                    ID = HtmlUtils.HtmlDecode(element.Attribute("id").Value),
                    ParentID = string.IsNullOrEmpty(parentId) ? null : parentId,
                    SortIndex = int.Parse(element.Attribute("sortindex").Value)
                };
                return group;
            } catch {
                // usually NullReferenceException, but if something's invalid the group will be invalid
                throw new Exception("Invalid group");
            }
        }

        public override string ToString() {
            return string.Format("Group '{0}' ({1})", Name, ID);
        }

        /// <summary>
        /// Creates a deep copy of this  group.
        /// </summary>
        /// <returns>A clone of this group.</returns>
        public virtual IGroup Clone() {
            GroupBase clone = new GroupBase() {
                ID = this.ID,
                Name = this.Name,
                Icon = this.Icon,
                ParentID = this.ParentID,
                SortIndex = this.SortIndex
            };
            foreach(IGroup childGroup in this.Groups) {
                clone.Groups.Add(childGroup.Clone());
            }
            foreach(IAccount account in this.Accounts) {
                clone.Accounts.Add(account.Clone());
            }
            return clone;
        }

        public int CompareTo(object obj) {
            if(obj == null) {
                return 1;
            } else {
                IGroup other = obj as IGroup;
                if(other == null) {
                    return -1;
                } else {
                    return string.Compare(this.Name, other.Name, StringComparison.InvariantCultureIgnoreCase);
                }
            }
        }
    }
}
